/*
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tools.perflib.heap;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class ClassObj extends Instance implements Comparable<ClassObj> {

    @NonNull
    final String mClassName;

    private final long mStaticFieldsOffset;

    long mSuperClassId;

    long mClassLoaderId;

    Field[] mFields;

    Field[] mStaticFields;

    private int mInstanceSize;

    @NonNull
    ArrayList<Instance> mInstances = new ArrayList<Instance>();

    @NonNull
    Set<ClassObj> mSubclasses = new HashSet<ClassObj>();

    public ClassObj(long id, @NonNull StackTrace stack, @NonNull String className,
            long staticFieldsOffset) {
        super(id, stack);
        mClassName = className;
        mStaticFieldsOffset = staticFieldsOffset;
    }

    public final void addSubclass(ClassObj subclass) {
        mSubclasses.add(subclass);
    }

    @NonNull
    public final Set<ClassObj> getSubclasses() {
        return mSubclasses;
    }

    public final void dumpSubclasses() {
        for (ClassObj subclass : mSubclasses) {
            System.out.println("     " + subclass.mClassName);
        }
    }

    @NonNull
    public final String toString() {
        return mClassName.replace('/', '.');
    }

    public final void addInstance(@NonNull Instance instance) {
        mInstances.add(instance);
    }

    public final void setSuperClassId(long superClass) {
        mSuperClassId = superClass;
    }

    public final void setClassLoaderId(long classLoader) {
        mClassLoaderId = classLoader;
    }

    public Field[] getFields() {
        return mFields;
    }

    public void setFields(@NonNull Field[] fields) {
        mFields = fields;
    }

    public void setStaticFields(@NonNull Field[] staticFields) {
        mStaticFields = staticFields;
    }

    public void setInstanceSize(int size) {
        mInstanceSize = size;
    }

    public int getInstanceSize() {
        return mInstanceSize;
    }

    @NonNull
    public Map<Field, Object> getStaticFieldValues() {
        Map<Field, Object> result = new HashMap<Field, Object>();
        getBuffer().setPosition(mStaticFieldsOffset);

        int numEntries = readUnsignedShort();
        for (int i = 0; i < numEntries; i++) {
            Field f = mStaticFields[i];

            readId();
            readUnsignedByte();

            Object value = readValue(f.getType());
            result.put(f, value);
        }
        return result;
    }

    public final void dump() {
        System.out.println("+----------  ClassObj dump for: " + mClassName);

        System.out.println("+-----  Static fields");
        Map<Field, Object> staticFields = getStaticFieldValues();
        for (Field field : staticFields.keySet()) {
            System.out.println(field.getName() + ": " + field.getType() + " = "
                    + staticFields.get(field));
        }

        System.out.println("+-----  Instance fields");
        for (Field field : mFields) {
            System.out.println(field.getName() + ": " + field.getType());
        }
        if (getSuperClassObj() != null) {
            getSuperClassObj().dump();
        }
    }

    @NonNull
    public final String getClassName() {
        return mClassName;
    }

    @Override
    public final void accept(@NonNull Visitor visitor) {
        if (visitor.visitEnter(this)) {
            for (Object value : getStaticFieldValues().values()) {
                if (value instanceof Instance) {
                    ((Instance) value).accept(visitor);
                }
            }
            visitor.visitLeave(this);
        }
    }

    @Override
    public final int compareTo(@NonNull ClassObj o) {
        return mClassName.compareTo(o.mClassName);
    }

    public final boolean equals(Object o) {
        if (!(o instanceof ClassObj)) {
            return false;
        }

        return 0 == compareTo((ClassObj) o);
    }

    @VisibleForTesting
    Object getStaticField(Type type, String name) {
        return getStaticFieldValues().get(new Field(type, name));
    }

    public ClassObj getSuperClassObj() {
        return mHeap.mSnapshot.findClass(mSuperClassId);
    }

    @Nullable
    public Instance getClassLoader() {
        return mHeap.mSnapshot.findReference(mClassLoaderId);
    }

    @NonNull
    public Collection<Instance> getInstances() {
        return mInstances;
    }
}
